/**
 * 自定义输出格式。
 * 标准库提供的printf()函数比较简单，有时程序需要一些自定义的输出格式，例如自动在用户打印的字符串前面加一些打印等级、标签等。
 * 例如打印一些提示/警告/错误信息：
 * "[ERROR/math] cannot divide zero"
 *
 * 需要用到的知识点：
 * 1）__VA_ARGS__
 *  在宏定义中使用，不能在函数中使用。如果函数中需要处理可变参数，请使用va_list+va_start+va_end函数
 *  可以从__VA_ARGS__中获取格式以及参数列表
 * 2）##__VA_ARGS__
 *  前面加##表示如果传入的可变参数为空，则让预处理器去掉它前面的逗号，避免可变参数为空时编译报错
 * 3）vsnprintf与snprintf
 *  由于printf只能输出到STDIO，用户无法进行后续处理， 这两个函数可以将打印输出到一个指定的字符数组中
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <stdarg.h>

/*************************************************************************************
 * __VA_ARGS__是一个可变参数宏，通常在宏定义中使用，用来将左边...的内容拷贝到右边
 * __VA_ARGS__只能是一些不含变量的字符串常量！如果包含了变量，输出会产生错误
 *
 *************************************************************************************/
#define MY_PRINT(...) printf(__VA_ARGS__)
void test01() {
    printf("================== %s ==================\n", __FUNCTION__);
    MY_PRINT("hello world: %s, %d\n", "info", 2); // 输出正确

    int a = 100;
    MY_PRINT("hello world: %s, %d\n", "info", a);
}

/*************************************************************************************
 * ##__VA_ARGS__
 * 前面加##表示如果传入的可变参数为空，则让预处理器去掉它前面的逗号，避免可变参数为空时编译报错。
 *
 *************************************************************************************/
#define MESSAGE_INFO(fmt, ...)                     \
    do {                                           \
        char out[128];                             \
        snprintf(out, 128, ">>> [INFO]: %s", fmt); \
        printf(out, ##__VA_ARGS__);                \
    } while (0)

#define MESSAGE_WARN(fmt, ...)                     \
    do {                                           \
        char out[128];                             \
        snprintf(out, 128, ">>> [WARN]: %s", fmt); \
        printf(out, __VA_ARGS__);                  \
    } while (0)

#define MY_WARN(fmt, ...)                                         \
    do {                                                          \
        char buf[128] = {0x00};                                   \
        int  len      = snprintf(buf, 128, "[%s]", __FUNCTION__); \
        snprintf(buf + len, 128 - len, "%s", fmt);                \
        printf(buf, ##__VA_ARGS__);                               \
    } while (0)

/**
 * 定义一个新的消息打印
 * printf()打印函数
 */
void test02() {
    printf("================== %s ==================\n", __FUNCTION__);
    MESSAGE_INFO("hello world\n");
    MESSAGE_INFO("%d, %d, %d\n", 1, 2, 3);

    // MESSAGE_WARN("hello world\n");      // 错误，造成__VA_ARGS__接收的参数个数为0
    MESSAGE_WARN("%d, %d, %d\n", 1, 2, 3); // 正确
}

/*************************************************************************************
 *
 * vsnprintf与snprintf
 * printf是将处理后的字符串输出到STDIO中，而这两个函数可以指定输出到一个字符数组中。
 *
 * snprintf: 不能传入可变参数列表，所有的参数都应该是确定的
 * vsnprintf：可以传入可变参数列表
 *************************************************************************************/
void print_message(const char *fmt, ...) {
    char fmt_f[128];
    int  len;

    va_list args;
    va_start(args, fmt);
    len = snprintf(fmt_f, sizeof(fmt_f), "%s", ">>> [WARNING] ");
    vsnprintf(fmt_f + len, sizeof(fmt_f) - len, fmt, args);
    va_end(args);
    printf("%s", fmt_f);
}

#define PRINT_WARN(...) print_message(__VA_ARGS__)

/**
 *
 */
void test03() {
    printf("================== %s ==================\n", __FUNCTION__);
    PRINT_WARN("hello world\n");
    PRINT_WARN("%d, %d, %d\n", 1, 2, 3);
}

/*************************************************************************************
 * 日志打印示例
 * 在要打印的字符串前面添加日志等级、日志标签
 *
 *************************************************************************************/
#define LOG_TAG      "test"
#define LOG_LVL_INFO 6

#define ulog_i(TAG, ...) ulog_output2(LOG_LVL_INFO, TAG, __VA_ARGS__)
#define LOG_I(...)       ulog_i(LOG_TAG, __VA_ARGS__)

void ulog_output(int level, const char *tag, const char *format, ...) {
    int  len = 0;
    char log_buf[128]; // 将所有数据一整块处理
    memset(log_buf, 0, sizeof(log_buf));

    switch (level) {
    case LOG_LVL_INFO:
        snprintf(log_buf, sizeof(log_buf), "%s/%s: ", "INFO", tag);
        break;
    default:
        break;
    }
    len = strlen(log_buf);

    va_list args;
    va_start(args, format);
    vsnprintf(log_buf + len, sizeof(log_buf) - len, format, args);
    va_end(args);

    printf("%s", log_buf);
    // va_list args;
    // va_start(args, format);
    // vsnprintf(log_buf, 128, format, args);
    // printf(log_buf);
    // va_end(args);
}

void ulog_output2(int level, const char *tag, const char *format, ...) {
    int  len = 0;
    char log_buf[128];      // 1）只处理输入部分
    char buf_level_tag[64]; // 2）将level和tag单独处理
    memset(log_buf, 0, sizeof(log_buf));

    switch (level) {
    case LOG_LVL_INFO:
        snprintf(buf_level_tag, sizeof(buf_level_tag), "%s/%s: ", "INFO", tag);

        break;
    default:
        break;
    }

    va_list args;
    va_start(args, format);
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    va_end(args);

    printf("%s%s", buf_level_tag, log_buf); // 将两块分别输出
}

#define LOG_W_TAG               "demo"
#define ulog_w(level, TAG, ...) ulog_w_output(#level, TAG, __VA_ARGS__) // 通过#将输入转成一个字符串
#define LOG_W(...)              ulog_w(INFO, LOG_W_TAG, __VA_ARGS__)
void ulog_w_output(char *level, const char *tag, const char *format, ...) {
    int  len = 0;
    char log_buf[128]; // 只处理输入部分
    memset(log_buf, 0, sizeof(log_buf));

    va_list args;
    va_start(args, format);
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    va_end(args);

    printf("[%s/%s]%s", level, tag, log_buf); // 将两块分别输出
}

void demo01() {
    printf("================== %s ==================\n", __FUNCTION__);
    LOG_I("hello world: %s, %d\n", "alex", 30);
}

void demo02() {
    printf("================== %s ==================\n", __FUNCTION__);
    LOG_W("hello world: %s, %d\n", "alex", 50);
}

int main(int argc, char *argv[]) {
    test01();
    test02();

    test03();

    demo01();
    demo02();

    MY_WARN("%s:%d\n", "alex", 18);
    printf("\n\n");
    return 0;
}